#include "pch-il2cpp.h"
#include "ESP.h"

#include <helpers.h>
#include <algorithm>

#include <cheat/events.h>
#include <cheat/game/EntityManager.h>
#include "ESPRender.h"
#include <cheat/game/filters.h>

#define IMGUI_DEFINE_MATH_OPERATORS
#include <imgui_internal.h>
#include <misc/cpp/imgui_stdlib.h>

#include "cheat-base/cheat/CheatManagerBase.h"

namespace cheat::feature
{

	ESP::ESP() : Feature(),
		NF(f_Enabled, "ESP", "ESP", false),

		NF(f_DrawBoxMode, "Draw Mode", "ESP", DrawMode::Box),
		NF(f_DrawTracerMode, "Tracer Mode", "ESP", DrawTracerMode::Line),
		NF(f_Fill, "Fill Box/Rectangle/Arrows", "ESP", false),
		NF(f_FillTransparency, "Fill Transparency", "ESP", 0.5f),

		NF(f_ArrowRadius, "Arrow Radius", "ESP", 100.0f),
		NF(f_OutlineThickness, "Outline Thickness", "ESP", 1.0f),
		NF(f_TracerSize, "Tracer Size", "ESP", 1.0f),
		NF(f_MiddleScreenTracer, "Middle Screen Tracer", "ESP", false),
		NF(f_DrawDistance, "Draw Distance", "ESP", false),
		NF(f_DrawName, "Draw Name", "ESP", false),

		NF(f_FontSize, "Font Size", "ESP", 12.0f),
		NF(f_FontOutline, "Font outline", "ESP", true),
		NF(f_FontOutlineSize, "Font outline size", "ESP", 1.0f),

		NF(f_GlobalFontColor, "Font Color", "ESP", ImColor(255, 255, 255)),
		NF(f_GlobalBoxColor, "Box Color", "ESP", ImColor(255, 255, 255)),
		NF(f_GlobalLineColor, "Tracer Color", "ESP", ImColor(255, 255, 255)),
		NF(f_GlobalRectColor, "Rect Color", "ESP", ImColor(255, 255, 255)),

		NF(f_MinSize, "Min. Entity Size", "ESP", 0.5f),
		NF(f_Range, "Range", "ESP", 100.0f),
		m_Search({})
	{
		InstallFilters();

		m_FontContrastColor = ImGui::CalcContrastColor(f_GlobalFontColor);

		::events::KeyUpEvent += MY_METHOD_HANDLER(ESP::OnKeyUp);
	}


	const FeatureGUIInfo& ESP::GetGUIInfo() const
	{
		static const FeatureGUIInfo info{ "", "ESP", false };
		return info;
	}

	void ESP::DrawMain()
	{
		if (ImGui::BeginGroupPanel("General", true))
		{
			ConfigWidget("ESP Enabled", f_Enabled, "Show filtered object through obstacles.");
			ConfigWidget("Range (m)", f_Range, 1.0f, 1.0f, 200.0f);

			ConfigWidget(f_DrawBoxMode, "Select the mode of box drawing.");
			ConfigWidget(f_DrawTracerMode, "Select the mode of tracer drawing.");

			ConfigWidget(f_Fill);
			ConfigWidget(f_FillTransparency, 0.01f, 0.0f, 1.0f, "Transparency of filled part.");
			ConfigWidget(f_MiddleScreenTracer, "Draw tracer from middle part of the screen.");

			if (f_DrawTracerMode.value() == DrawTracerMode::OffscreenArrows)
			{
				if (ImGui::BeginGroupPanel("Arrow tracer options", true))
				{
					ConfigWidget(f_TracerSize, 0.005f, 0.1f, 10.0f, "Size of tracer.");
					ConfigWidget(f_ArrowRadius, 0.5f, 50.0f, 300.0f, "Radius of arrow.");
					ConfigWidget(f_OutlineThickness, 0.005f, 0.0f, 10.0f, "Outline thickness of arrow.");
				}
				ImGui::EndGroupPanel();
			}

			ImGui::Spacing();
			ConfigWidget(f_DrawName, "Draw name of object.");
			ConfigWidget(f_DrawDistance, "Draw distance of object.");

			ImGui::Spacing();
			ConfigWidget(f_FontSize, 1, 1, 100, "Font size of name or distance.");
			ConfigWidget("## Font outline enabled", f_FontOutline); ImGui::SameLine();
			ConfigWidget("Font outline", f_FontOutlineSize, 0.001f, 0.0f, 10.0f);

			ImGui::Spacing();
			if (ImGui::BeginGroupPanel("Global colors", true))
			{
				if (ConfigWidget(f_GlobalFontColor, "Color of line, name, or distance text font."))
					m_FontContrastColor = ImGui::CalcContrastColor(f_GlobalFontColor);

				ConfigWidget(f_GlobalBoxColor, "Color of box font.");
				ConfigWidget(f_GlobalLineColor, "Color of line font.");
				ConfigWidget(f_GlobalRectColor, "Color of rectangle font.");
			}
			ImGui::EndGroupPanel();

			ConfigWidget(f_MinSize, 0.05f, 0.1f, 200.0f, "Minimum entity size as measured in-world.\n" \
				"Some entities have either extremely small or no bounds at all.\n" \
				"This parameter helps filter out entities that don't meet this condition.");
		}
		ImGui::EndGroupPanel();

		ImGui::Text("How to use item filters:\n\tLMB - Toggle visibility\n\tRMB - Open color picker");
		ImGui::InputText("Search Filters", &m_Search);

		for (auto& [section, filters] : m_Sections)
		{
			ImGui::PushID(section.c_str());
			DrawSection(section, filters);
			ImGui::PopID();
		}
	}

	bool ESP::NeedStatusDraw() const
	{
		return f_Enabled;
	}

	void ESP::DrawStatus()
	{
		ImGui::Text("ESP [%.01fm|%s|%s%s%s%s]",
			f_Range.value(),
			f_DrawBoxMode.value() == DrawMode::Box ? "Box" : f_DrawBoxMode.value() == DrawMode::Rectangle ? "Rect" : "None",
			f_Fill ? "F" : "",
			f_DrawTracerMode.value() == DrawTracerMode::Line ? "L" : f_DrawTracerMode.value() == DrawTracerMode::OffscreenArrows ? "A" : "",
			f_DrawName ? "N" : "",
			f_DrawDistance ? "D" : ""
		);
	}

	ESP& ESP::GetInstance()
	{
		static ESP instance;
		return instance;
	}

    bool ESP::isBuriedChest(game::Entity* entity)
    {
        if (entity->name().find("_WorldArea_Operator") != std::string::npos)
        {
            auto entityGameObject = app::MoleMole_BaseEntity_get_rootGameObject(entity->raw(), nullptr);
            auto transform = app::GameObject_GetComponentByName(entityGameObject, string_to_il2cppi("Transform"), nullptr);
            auto child = app::Transform_FindChild(reinterpret_cast<app::Transform*>(transform), string_to_il2cppi("CircleR2H2"), nullptr);
            if (child == nullptr)
                return false;

			auto configID = entity->raw()->fields._configID_k__BackingField;
			//LOG_DEBUG("%d", configID);
			if (configID != 70360001 && configID != 70360286)
				return false;

            return true;
        }
        return false;
    }

	void ESP::GetNpcName(std::string& name)
	{
		if (name.find("Avatar") != std::string::npos)
		{
			//cause name is like "Avatar_Catalyst_Boy_Heizo(Clone)" - We'll get name between 3rd underscore and 1st bracket
			int  j = 0;		// j is the number of spaces before the name starts
			int pos1 = 0;
			int pos2 = 0;
			for (int i = 0; i < name.length(); i++)
			{
				if (name[i] == '_')
				{
					j++;
					if (j == 3)
					{
						pos1 = i;
					}

				}
				if (name[i] == '(')
				{
					pos2 = i;
					break;
				}
			}
			name = name.substr(pos1 + 1, pos2 - pos1 - 1);
		}
		else if (name.find("Animal") != std::string::npos)
		{
			int count = 0;
			int  j = 0;
			int pos1 = 0;
			int pos2 = 0;
			for (int i = 0; i < name.length(); i++)
			{
				if (name[i] == '_')
				{
					count++;
				}
			}
			//switch statement to determine how we will get name
			switch (count)
			{
			case 3:
			{
				j = 0;		// j is the number of spaces before the name starts
				pos1 = 0;
				pos2 = 0;
				for (int i = 0; i < name.length(); i++)
				{
					if (name[i] == '_')
					{
						j++;
						if (j == 3)
						{
							pos1 = i;
						}

					}
					if (name[i] == '(')
					{
						pos2 = i;
						break;
					}
				}
				name = name.substr(pos1, pos2 - pos1);
			}
			case 4:
			{
				j = 0;		// j is the number of spaces before the name starts
				pos1 = 0;
				pos2 = 0;
				for (int i = 0; i < name.length(); i++)
				{
					if (name[i] == '_')
					{
						j++;
						if (j == 3)
						{
							pos1 = i;
						}
						if (j == 4)
						{
							pos2 = i;
							break;
						}
					}
				}
				name = name.substr(pos1 + 1, pos2 - pos1 - 1);
			}
			default:
				break;
			}
			return;
		}
		else if (name.find("Monster") != std::string::npos)
		{
			int  j = 0;     //number of underscores in the name
			int pos1 = 0;	//position of the first underscore in the name
			int pos2 = 0;   //position of the second underscore in the name
			for (int i = 0; i < name.length(); i++)
			{
				if (name[i] == '_')
				{
					j++;
					if (j == 3)
					{
						pos1 = i;
					}
					if (j == 4)
					{
						pos2 = i;
						break;
					}
				}
			}
			name = name.substr(pos1 + 1, pos2 - pos1 - 1);
		}	
		else if (name.find("Aranara") != std::string::npos)
		{
		char AA[] = "Aranara";
		name = AA;
		}
		else if (name.find("Kanban") != std::string::npos)
		{
		char AA[] = "Paimon";
		name = AA;
		}
		else
		{
			int  j = 0;     //number of underscores in the name
			int pos1 = 0;	//position of the first underscore in the name
			int pos2 = 0;   //position of the second underscore in the name
			for (int i = 0; i < name.length(); i++)
			{
				if (name[i] == '_')
				{
					j++;
					if (j == 4)
					{
						pos1 = i;
					}
					if (j == 5)
					{
						pos2 = i;
						break;
					}
				}
			}
			name = name.substr(pos1 + 1, pos2 - pos1 - 1);
		}
		return;
	}

	void ESP::AddFilter(const std::string& section, const std::string& name, game::IEntityFilter* filter)
	{
		if (m_Sections.count(section) == 0)
			m_Sections[section] = {};

		auto& filters = m_Sections[section];
		esp::ESPItem newItem(name, ImColor(120, 120, 120, 255), {}, name);
		filters.push_back({ config::CreateField<esp::ESPItem>(name, name, fmt::format("ESP::Filters::{}", section), false, newItem), filter });
	}

	void ESP::DrawSection(const std::string& section, const Filters& filters)
	{
		std::vector<const FilterInfo*> validFilters;

		for (auto& info : filters)
		{
			const auto& filterName = info.first.value().m_Name;

			auto it = std::search(
				filterName.begin(), filterName.end(),
				m_Search.begin(), m_Search.end(),
				[](char ch1, char ch2) { return std::tolower(ch1) == std::tolower(ch2); }
			);

			if (it != filterName.end())
				validFilters.push_back(&info);
		}

		if (validFilters.empty())
			return;

		bool checked = std::all_of(validFilters.begin(), validFilters.end(), [](const FilterInfo* filter) {  return filter->first.value().m_Enabled; });
		bool changed = false;

		if (ImGui::BeginSelectableGroupPanel(section.c_str(), checked, changed, true))
		{
			for (auto& info : validFilters)
			{
				ImGui::PushID(info);
				DrawFilterField(info->first);
				ImGui::PopID();
			}

			ImGui::Spacing();

			if (ImGui::TreeNode(this, "Hotkeys"))
			{
				for (auto& info : validFilters)
				{
					auto& field = info->first;
					ImGui::PushID(info);

					auto& hotkey = field.value().m_Hotkey;
					if (InputHotkey(field.name().c_str(), &hotkey, true))
						field.FireChanged();

					ImGui::PopID();
				}

				ImGui::TreePop();
			}
		}
		ImGui::EndSelectableGroupPanel();

		if (changed)
		{
			for (auto& info : validFilters)
			{
				info->first.value().m_Enabled = checked;
				info->first.FireChanged();
			}
		}
	}

	std::string Unsplit(const std::string& value)
	{
		std::stringstream in(value);
		std::stringstream out;
		std::string word;
		while (in >> word)
			out << word;
		return out.str();
	}

	void FilterItemSelector(const char* label, ImTextureID image, const config::Field<esp::ESPItem>& field, const ImVec2& size = ImVec2(200, 0), float icon_size = 30);

	void ESP::DrawFilterField(const config::Field<esp::ESPItem>& field)
	{
		auto imageInfo = ImageLoader::GetImage(Unsplit(field.value().m_Name));
		FilterItemSelector(field.value().m_Name.c_str(), imageInfo ? imageInfo->textureID : nullptr, field);
	}

	void ESP::DrawExternal()
	{
		auto& esp = ESP::GetInstance();
		if (!esp.f_Enabled)
			return;

		esp::render::PrepareFrame();

		auto& entityManager = game::EntityManager::instance();

		for (auto& entity : entityManager.entities())
		{
			if (entityManager.avatar()->distance(entity) > esp.f_Range)
				continue;

			for (auto& [section, filters] : m_Sections)
			{
				for (auto& [field, filter] : filters)
				{
					auto& entry = field.value();
					if (!entry.m_Enabled || !m_FilterExecutor.ApplyFilter(entity, filter))
						continue;
                    if (entry.m_Name == "Buried Chest")
					{
                        if(isBuriedChest(entity))
                        {
                            esp::render::DrawEntity(entry.m_Name, entity, entry.m_Color, entry.m_ContrastColor);
                        }
                        break;
                    }
					if (entry.m_Name == "Npc" || "AvatarOwn" || "AvatarTeammate")
					{
                        if (isBuriedChest(entity))
                            continue;
						if (entity->type() == app::EntityType__Enum_1::Avatar || entity->type() == app::EntityType__Enum_1::NPC)
						{
							std::string name = entity->name();
							GetNpcName(name);
							esp::render::DrawEntity(name, entity, entry.m_Color, entry.m_ContrastColor);
							break;
						}
					}
					esp::render::DrawEntity(entry.m_Name, entity, entry.m_Color, entry.m_ContrastColor);
					break;
				}
			}
		}
	}

	void ESP::OnKeyUp(short key, bool& cancelled)
	{
		for (auto& [section, filters] : m_Sections)
		{
			for (auto& [field, filter] : filters)
			{
				auto& entry = field.value();
				if (entry.m_Hotkey.IsPressed(key))
				{
					entry.m_Enabled = !entry.m_Enabled;
					field.FireChanged();
				}
			}
		}
	}

	void FilterItemSelector(const char* label, ImTextureID image, const config::Field<esp::ESPItem>& field, const ImVec2& size, float icon_size)
	{

		// Init ImGui
		ImGuiWindow* window = ImGui::GetCurrentWindow();
		if (window->SkipItems)
			return;

		ImGuiContext& g = *GImGui;
		ImGuiIO& io = g.IO;
		const ImGuiStyle& style = g.Style;
		const ImGuiID id = window->GetID(label);

		const ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true);
		const ImVec2 item_size = ImGui::CalcItemSize(size, ImGui::CalcItemWidth(), label_size.y + style.FramePadding.y * 2.0f + 20.0f);

		float region_max_x = ImGui::GetContentRegionMaxAbs().x;
		if (region_max_x - window->DC.CursorPos.x < item_size.x)
			ImGui::Spacing();

		const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + item_size);
		const ImRect total_bb(window->DC.CursorPos, { frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Max.y });

		ImGui::ItemSize(total_bb, style.FramePadding.y);
		if (!ImGui::ItemAdd(total_bb, id))
		{
			ImGui::SameLine();
			return;
		}
		const bool hovered = ImGui::ItemHoverable(frame_bb, id);
		if (hovered)
		{
			ImGui::SetHoveredID(id);
			g.MouseCursor = ImGuiMouseCursor_Hand;
		}

		const bool lmb_click = hovered && io.MouseClicked[0];
		if (lmb_click)
		{
			field.value().m_Enabled = !field.value().m_Enabled;
			field.FireChanged();
			ImGui::FocusWindow(window);
			memset(io.MouseDown, 0, sizeof(io.MouseDown));
		}

		const bool rmb_click = hovered && io.MouseClicked[ImGuiMouseButton_Right];

		ImGuiWindow* picker_active_window = NULL;

		static bool color_changed = false;
		static ImGuiID opened_id = 0;
		if (rmb_click)
		{
			// Store current color and open a picker
			g.ColorPickerRef = ImVec4(field.value().m_Color);
			ImGui::OpenPopup("picker");
			ImGui::SetNextWindowPos(g.LastItemData.Rect.GetBL() + ImVec2(0.0f, style.ItemSpacing.y));
			opened_id = id;
			memset(io.MouseDown, 0, sizeof(io.MouseDown));
		}

		if (ImGui::BeginPopup("picker"))
		{
			picker_active_window = g.CurrentWindow;
			ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_PickerMask_ | ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar;
			ImGuiColorEditFlags picker_flags = ImGuiColorEditFlags_DisplayMask_ | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf;
			ImGui::SetNextItemWidth(ImGui::GetFrameHeight() * 12.0f); // Use 256 + bar sizes?
			color_changed |= ImGui::ColorPicker4("##picker", reinterpret_cast<float*>(&field.value().m_Color), picker_flags, &g.ColorPickerRef.x);
			ImGui::EndPopup();
		}

		bool popup_closed = id == opened_id && picker_active_window == NULL;
		if (popup_closed)
		{
			opened_id = 0;
			if (color_changed)
			{
				field.value().m_ContrastColor = ImGui::CalcContrastColor(field.value().m_Color);
				field.FireChanged();
				color_changed = false;
			}
		}

		const ImU32 border_color = ImGui::GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
		const ImRect clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + item_size.x, frame_bb.Min.y + item_size.y); // Not using frame_bb.Max because we have adjusted size

		float border_size = 2.0f;
		float border_rounding = 10.0f;

		ImVec2 render_pos = frame_bb.Min + style.FramePadding;
		ImGui::RenderFrame(clip_rect.Min, clip_rect.Max, field.value().m_Color, false, border_rounding);

		if (field.value().m_Enabled)
		{
			float check_mark_size = 13.0f;
			ImVec2 checkStart = { clip_rect.Max.x - check_mark_size - border_size, clip_rect.Min.y };
			ImGui::RenderFrame(checkStart - ImVec2(2.0f, 0), checkStart + ImVec2(check_mark_size + border_size, check_mark_size + border_size + 2.0f),
				ImGui::GetColorU32(ImGuiCol_FrameBg), false, border_rounding);
			ImGui::RenderCheckMark(window->DrawList, checkStart + ImVec2(0, 1.0f), 0xFFFFFFFF, check_mark_size - 1.0f);
		}

		window->DrawList->AddRect(clip_rect.Min, clip_rect.Max, border_color, border_rounding - 1.0f, 0, border_size);

		float y_center = frame_bb.Min.y + (frame_bb.Max.y - frame_bb.Min.y) / 2;

		ImVec2 image_start(frame_bb.Min.x + style.FramePadding.x + 5.0f, y_center - icon_size / 2);
		ImVec2 image_end(image_start.x + icon_size, image_start.y + icon_size);
		if (image != nullptr)
			window->DrawList->AddImageRounded(image, image_start, image_start + ImVec2(icon_size, icon_size), { 0.0f, 0.0f }, { 1.0f, 1.0f },
				ImColor(1.0f, 1.0f, 1.0f), 0.3f);

		bool pushed = ImGui::PushStyleColorWithContrast(field.value().m_Color, ImGuiCol_Text, ImColor(0, 0, 0), 2.0f);

		ImVec2 text_end(frame_bb.Max.x - style.FramePadding.x - border_size, y_center + label_size.y / 2);
		ImVec2 text_start(ImMax(image_end.x + style.FramePadding.x, text_end.x - label_size.x), y_center - label_size.y / 2);
		ImGui::RenderTextClipped(text_start, text_end, label, NULL, NULL, { 0, 0 }, &clip_rect);

		if (pushed)
			ImGui::PopStyleColor();

		ImGui::SameLine();

		return;
	}

#define ADD_FILTER_FIELD(section, name) AddFilter(util::MakeCapital(#section), util::SplitWords(#name), &game::filters::##section##::##name##)
	void ESP::InstallFilters()
	{
		ADD_FILTER_FIELD(collection, Book);
		ADD_FILTER_FIELD(collection, Viewpoint);
		ADD_FILTER_FIELD(collection, RadiantSpincrystal);
		ADD_FILTER_FIELD(collection, BookPage);
		ADD_FILTER_FIELD(collection, QuestInteract);
		ADD_FILTER_FIELD(collection, WoodenCrate);
		ADD_FILTER_FIELD(collection, GeoSigil);

		// Regular Chests
		ADD_FILTER_FIELD(chest, CommonChest);
		ADD_FILTER_FIELD(chest, ExquisiteChest);
		ADD_FILTER_FIELD(chest, PreciousChest);
		ADD_FILTER_FIELD(chest, LuxuriousChest);
		ADD_FILTER_FIELD(chest, RemarkableChest);
		// Other Chests
		ADD_FILTER_FIELD(chest, BuriedChest);
		ADD_FILTER_FIELD(chest, SearchPoint);
		

		ADD_FILTER_FIELD(featured, Anemoculus);
		ADD_FILTER_FIELD(featured, CrimsonAgate);
		ADD_FILTER_FIELD(featured, Dendroculus);
		ADD_FILTER_FIELD(featured, Electroculus);
		ADD_FILTER_FIELD(featured, Electrogranum);
		ADD_FILTER_FIELD(featured, FishingPoint);
		ADD_FILTER_FIELD(featured, Geoculus);
		ADD_FILTER_FIELD(featured, ItemDrops);
		ADD_FILTER_FIELD(featured, KeySigil);
		ADD_FILTER_FIELD(featured, Lumenspar);
		ADD_FILTER_FIELD(featured, ShrineOfDepth);
		ADD_FILTER_FIELD(featured, TimeTrialChallenge);

		ADD_FILTER_FIELD(guide, BouncyMushroom);
		ADD_FILTER_FIELD(guide, CampfireTorch);
		ADD_FILTER_FIELD(guide, ClusterleafOfCultivation);
		ADD_FILTER_FIELD(guide, DayNightSwitchingMechanism);
		ADD_FILTER_FIELD(guide, DendroGranum);
		ADD_FILTER_FIELD(guide, DendroPile);
		ADD_FILTER_FIELD(guide, DendroRock);
		ADD_FILTER_FIELD(guide, EnkanomiyaPhaseGate);
		ADD_FILTER_FIELD(guide, MysteriousCarvings);
		ADD_FILTER_FIELD(guide, PhaseGate);
		ADD_FILTER_FIELD(guide, PlacesofEssenceWorship);
		ADD_FILTER_FIELD(guide, Pot);
		ADD_FILTER_FIELD(guide, RuinBrazier);
		ADD_FILTER_FIELD(guide, Stormstone);
		ADD_FILTER_FIELD(guide, TriangularMechanism);

		ADD_FILTER_FIELD(living, AvatarTeammate);
		ADD_FILTER_FIELD(living, AvatarOwn);
		ADD_FILTER_FIELD(living, BakeDanuki);
		ADD_FILTER_FIELD(living, BirdEgg);
		ADD_FILTER_FIELD(living, Boar);
		ADD_FILTER_FIELD(living, ButterflyWings);
		ADD_FILTER_FIELD(living, Cat);
		ADD_FILTER_FIELD(living, Crab);
		ADD_FILTER_FIELD(living, Crane);
		ADD_FILTER_FIELD(living, Crow);
		ADD_FILTER_FIELD(living, CrystalCore);
		ADD_FILTER_FIELD(living, Dog);
		ADD_FILTER_FIELD(living, DuskBird);
		ADD_FILTER_FIELD(living, Eel);
		ADD_FILTER_FIELD(living, Falcon);
		ADD_FILTER_FIELD(living, Finch);
		ADD_FILTER_FIELD(living, Fish);
		ADD_FILTER_FIELD(living, Fox);
		ADD_FILTER_FIELD(living, Frog);
		ADD_FILTER_FIELD(living, Inu);
		ADD_FILTER_FIELD(living, Kitsune);
		ADD_FILTER_FIELD(living, LizardTail);
		ADD_FILTER_FIELD(living, LucklightFly);
		ADD_FILTER_FIELD(living, LuminescentSpine);
		ADD_FILTER_FIELD(living, Npc);
		ADD_FILTER_FIELD(living, Onikabuto);
		ADD_FILTER_FIELD(living, Pigeon);
		ADD_FILTER_FIELD(living, Salamander);
		ADD_FILTER_FIELD(living, Scarab);
		ADD_FILTER_FIELD(living, Squirrel);
		ADD_FILTER_FIELD(living, Starconch);
		ADD_FILTER_FIELD(living, Weasel);
		ADD_FILTER_FIELD(living, Wigeon);

		ADD_FILTER_FIELD(mineral, AmethystLump);
		ADD_FILTER_FIELD(mineral, ArchaicStone);
		ADD_FILTER_FIELD(mineral, CorLapis);
		ADD_FILTER_FIELD(mineral, CrystalChunk);
		ADD_FILTER_FIELD(mineral, CrystalMarrow);
		ADD_FILTER_FIELD(mineral, DunlinsTooth);
		ADD_FILTER_FIELD(mineral, ElectroCrystal);
		ADD_FILTER_FIELD(mineral, IronChunk);
		ADD_FILTER_FIELD(mineral, NoctilucousJade);
		ADD_FILTER_FIELD(mineral, MagicalCrystalChunk);
		ADD_FILTER_FIELD(mineral, ScarletQuartz);
		ADD_FILTER_FIELD(mineral, Starsilver);
		ADD_FILTER_FIELD(mineral, WhiteIronChunk);

		// Trounce. Arranged by appearance in-game.
		ADD_FILTER_FIELD(monster, Dvalin);
		ADD_FILTER_FIELD(monster, Andrius);
		ADD_FILTER_FIELD(monster, Tartaglia);
		ADD_FILTER_FIELD(monster, Azhdaha);
		ADD_FILTER_FIELD(monster, Signora);
		ADD_FILTER_FIELD(monster, Shogun);
		// Bosses. Arranged by "type" then alphabetical.
		// Regisvines.
		ADD_FILTER_FIELD(monster, CryoRegisvine);
		ADD_FILTER_FIELD(monster, ElectroRegisvine);
		ADD_FILTER_FIELD(monster, PyroRegisvine);
		// Hypostases.
		ADD_FILTER_FIELD(monster, AnemoHypostasis);
		ADD_FILTER_FIELD(monster, CryoHypostasis);
		ADD_FILTER_FIELD(monster, ElectroHypostasis);
		ADD_FILTER_FIELD(monster, GeoHypostasis);
		ADD_FILTER_FIELD(monster, HydroHypostasis);
		ADD_FILTER_FIELD(monster, PyroHypostasis);
		// Vishaps.
		ADD_FILTER_FIELD(monster, CryoBathysmalVishap);
		ADD_FILTER_FIELD(monster, ElectroBathysmalVishap);
		ADD_FILTER_FIELD(monster, PrimoGeovishap);
		// Oceanids.
		ADD_FILTER_FIELD(monster, Oceanid);
		ADD_FILTER_FIELD(monster, ThunderManifestation);
		// Khaenri'ah.
		ADD_FILTER_FIELD(monster, PerpetualMechanicalArray);
		ADD_FILTER_FIELD(monster, RuinSerpent);
		// Others.
		ADD_FILTER_FIELD(monster, GoldenWolflord);
		ADD_FILTER_FIELD(monster, MaguuKenki);
		// Sumeru
		ADD_FILTER_FIELD(monster, JadeplumeTerrorshroom);
		ADD_FILTER_FIELD(monster, AeonblightDrake);
		// Regular. Alphabetical.
		ADD_FILTER_FIELD(monster, AbyssMage);
		ADD_FILTER_FIELD(monster, BlackSerpentKnight);
		ADD_FILTER_FIELD(monster, Cicin);
		ADD_FILTER_FIELD(monster, ElectroAbyssLector);
		ADD_FILTER_FIELD(monster, Eremite);
		ADD_FILTER_FIELD(monster, EyeOfTheStorm);
		ADD_FILTER_FIELD(monster, FatuiAgent);
		ADD_FILTER_FIELD(monster, FatuiCicinMage);
		ADD_FILTER_FIELD(monster, FatuiMirrorMaiden);
		ADD_FILTER_FIELD(monster, FatuiSkirmisher);
		ADD_FILTER_FIELD(monster, FloatingFungus);
		ADD_FILTER_FIELD(monster, Geovishap);
		ADD_FILTER_FIELD(monster, GeovishapHatchling);
		ADD_FILTER_FIELD(monster, GroundedShroom);
		ADD_FILTER_FIELD(monster, Hilichurl);
		ADD_FILTER_FIELD(monster, HydroAbyssHerald);
		ADD_FILTER_FIELD(monster, HydroBathysmalVishap);
		ADD_FILTER_FIELD(monster, Kairagi);
		ADD_FILTER_FIELD(monster, Millelith);
		ADD_FILTER_FIELD(monster, Mitachurl);
		ADD_FILTER_FIELD(monster, Nobushi);
		ADD_FILTER_FIELD(monster, OceanidBoar);
		ADD_FILTER_FIELD(monster, OceanidCrab);
		ADD_FILTER_FIELD(monster, OceanidCrane);
		ADD_FILTER_FIELD(monster, OceanidFalcon);
		ADD_FILTER_FIELD(monster, OceanidFinch);
		ADD_FILTER_FIELD(monster, OceanidFrog);
		ADD_FILTER_FIELD(monster, OceanidSquirrel);
		ADD_FILTER_FIELD(monster, OceanidWigeon);
		ADD_FILTER_FIELD(monster, PrimalConstruct);
		ADD_FILTER_FIELD(monster, PyroAbyssLector);
		ADD_FILTER_FIELD(monster, Rifthound);
		ADD_FILTER_FIELD(monster, RifthoundWhelp);
		ADD_FILTER_FIELD(monster, RishbolandTiger);
		ADD_FILTER_FIELD(monster, RuinDrake);
		ADD_FILTER_FIELD(monster, RuinGrader);
		ADD_FILTER_FIELD(monster, RuinGuard);
		ADD_FILTER_FIELD(monster, RuinHunter);
		ADD_FILTER_FIELD(monster, RuinSentinel);
		ADD_FILTER_FIELD(monster, Samachurl);
		ADD_FILTER_FIELD(monster, SangonomiyaCohort);
		ADD_FILTER_FIELD(monster, Scorpion);
		ADD_FILTER_FIELD(monster, SemiPerpetualControlMatrix);
		ADD_FILTER_FIELD(monster, ShadowyHusk);
		ADD_FILTER_FIELD(monster, ShaggySumpterBeast);
		ADD_FILTER_FIELD(monster, ShogunateInfantry);
		ADD_FILTER_FIELD(monster, Slime);
		ADD_FILTER_FIELD(monster, Specter);
		ADD_FILTER_FIELD(monster, Spincrocodile);
		ADD_FILTER_FIELD(monster, StretchyFungus);
		ADD_FILTER_FIELD(monster, TreasureHoarder);
		ADD_FILTER_FIELD(monster, UnusualHilichurl);
		ADD_FILTER_FIELD(monster, Vulture);
		ADD_FILTER_FIELD(monster, WhirlingFungus);
		ADD_FILTER_FIELD(monster, Whopperflower);
		ADD_FILTER_FIELD(monster, WingedShroom);

		ADD_FILTER_FIELD(plant, Ajilenakh);
		ADD_FILTER_FIELD(plant, AmakumoFruit);
		ADD_FILTER_FIELD(plant, Apple);
		ADD_FILTER_FIELD(plant, BambooShoot);
		ADD_FILTER_FIELD(plant, Berry);
		ADD_FILTER_FIELD(plant, CallaLily);
		ADD_FILTER_FIELD(plant, Carrot);
		ADD_FILTER_FIELD(plant, Cecilia);
		ADD_FILTER_FIELD(plant, DandelionSeed);
		ADD_FILTER_FIELD(plant, Dendrobium);
		ADD_FILTER_FIELD(plant, FlamingFlowerStamen);
		ADD_FILTER_FIELD(plant, FluorescentFungus);
		ADD_FILTER_FIELD(plant, GlazeLily);
		ADD_FILTER_FIELD(plant, HarraFruit);
		ADD_FILTER_FIELD(plant, Horsetail);
		ADD_FILTER_FIELD(plant, JueyunChili);
		ADD_FILTER_FIELD(plant, KalpalataLotus);
		ADD_FILTER_FIELD(plant, LavenderMelon);
		ADD_FILTER_FIELD(plant, LotusHead);
		ADD_FILTER_FIELD(plant, Matsutake);
		ADD_FILTER_FIELD(plant, Mint);
		ADD_FILTER_FIELD(plant, MistFlowerCorolla);
		ADD_FILTER_FIELD(plant, Mushroom);
		ADD_FILTER_FIELD(plant, NakuWeed);
		ADD_FILTER_FIELD(plant, NilotpalaLotus);
		ADD_FILTER_FIELD(plant, Padisarah);
		ADD_FILTER_FIELD(plant, PhilanemoMushroom);
		ADD_FILTER_FIELD(plant, Pinecone);
		ADD_FILTER_FIELD(plant, Qingxin);
		ADD_FILTER_FIELD(plant, Radish);
		ADD_FILTER_FIELD(plant, Redcrest);
		ADD_FILTER_FIELD(plant, RukkhashavaMushroom);
		ADD_FILTER_FIELD(plant, SakuraBloom);
		ADD_FILTER_FIELD(plant, SangoPearl);
		ADD_FILTER_FIELD(plant, SeaGanoderma);
		ADD_FILTER_FIELD(plant, Seagrass);
		ADD_FILTER_FIELD(plant, SilkFlower);
		ADD_FILTER_FIELD(plant, SmallLampGrass);
		ADD_FILTER_FIELD(plant, Snapdragon);
		ADD_FILTER_FIELD(plant, SumeruRose);
		ADD_FILTER_FIELD(plant, Sunsettia);
		ADD_FILTER_FIELD(plant, SweetFlower);
		ADD_FILTER_FIELD(plant, Valberry);
		ADD_FILTER_FIELD(plant, Violetgrass);
		ADD_FILTER_FIELD(plant, Viparyas);
		ADD_FILTER_FIELD(plant, WindwheelAster);
		ADD_FILTER_FIELD(plant, Wolfhook);
		ADD_FILTER_FIELD(plant, ZaytunPeach);

		ADD_FILTER_FIELD(puzzle, AncientRime);
		ADD_FILTER_FIELD(puzzle, BakeDanuki);
		ADD_FILTER_FIELD(puzzle, BloattyFloatty);
		ADD_FILTER_FIELD(puzzle, CubeDevices);
		ADD_FILTER_FIELD(puzzle, SumeruPuzzles);
		ADD_FILTER_FIELD(puzzle, TheWithering);
		ADD_FILTER_FIELD(puzzle, EightStoneTablets);
		ADD_FILTER_FIELD(puzzle, ElectricConduction);
		ADD_FILTER_FIELD(puzzle, ElectroSeelie);
		ADD_FILTER_FIELD(puzzle, ElementalMonument);
		ADD_FILTER_FIELD(puzzle, FloatingAnemoSlime);
		ADD_FILTER_FIELD(puzzle, Geogranum);
		ADD_FILTER_FIELD(puzzle, GeoPuzzle);
		ADD_FILTER_FIELD(puzzle, LargeRockPile);
		ADD_FILTER_FIELD(puzzle, LightningStrikeProbe);
		ADD_FILTER_FIELD(puzzle, LightUpTilePuzzle);
		ADD_FILTER_FIELD(puzzle, LumenCage);
		ADD_FILTER_FIELD(puzzle, LuminousSeelie);
		ADD_FILTER_FIELD(puzzle, MistBubble);
		ADD_FILTER_FIELD(puzzle, NurseriesInTheWilds);
		ADD_FILTER_FIELD(puzzle, OozingConcretions);
		ADD_FILTER_FIELD(puzzle, PhantasmalGate);
		ADD_FILTER_FIELD(puzzle, PirateHelm);
		ADD_FILTER_FIELD(puzzle, PressurePlate);
		ADD_FILTER_FIELD(puzzle, RelayStone);
		ADD_FILTER_FIELD(puzzle, SaghiraMachine);
		ADD_FILTER_FIELD(puzzle, SealLocations);
		ADD_FILTER_FIELD(puzzle, Seelie);
		ADD_FILTER_FIELD(puzzle, SeelieLamp);
		ADD_FILTER_FIELD(puzzle, SmallRockPile);
		ADD_FILTER_FIELD(puzzle, StonePillarSeal);
		ADD_FILTER_FIELD(puzzle, StormBarrier);
		ADD_FILTER_FIELD(puzzle, SwordHilt);
		ADD_FILTER_FIELD(puzzle, Temari);
		ADD_FILTER_FIELD(puzzle, TorchPuzzle);
		ADD_FILTER_FIELD(puzzle, TriYanaSeeds);
		ADD_FILTER_FIELD(puzzle, UniqueRocks);
		ADD_FILTER_FIELD(puzzle, WarmingSeelie);
		ADD_FILTER_FIELD(puzzle, WindmillMechanism);
	}
#undef ADD_FILTER_FIELD
}